﻿using System;
using System.Security.Cryptography;
using System.Text;

public class GitLab2FAClient
{
    private readonly string _twoFactorSecret;
    private const int TimeStepSeconds = 30; // TOTP 时间步长（秒）

    public GitLab2FAClient(string twoFactorSecret)
    {
        _twoFactorSecret = twoFactorSecret;
    }

    // 生成当前时间的 TOTP 验证码及剩余有效期
    public (string Code, int TimeRemainingSeconds) GenerateTOTPCodeWithExpiry()
    {
        var unixTimestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
        var timeStep = unixTimestamp / TimeStepSeconds;
        var timeRemaining = TimeStepSeconds - (int)(unixTimestamp % TimeStepSeconds);

        var keyBytes = Base32Decode(_twoFactorSecret);
        var timeStepBytes = BitConverter.GetBytes(timeStep);

        if (BitConverter.IsLittleEndian)
            Array.Reverse(timeStepBytes);

        using (var hmac = new HMACSHA1(keyBytes))
        {
            var hash = hmac.ComputeHash(timeStepBytes);

            int offset = hash[hash.Length - 1] & 0x0F;
            int binary =
                ((hash[offset] & 0x7F) << 24) |
                ((hash[offset + 1] & 0xFF) << 16) |
                ((hash[offset + 2] & 0xFF) << 8) |
                (hash[offset + 3] & 0xFF);

            int code = binary % 1000000;
            return (code.ToString("D6"), timeRemaining);
        }
    }

    // Base32 解码实现
    private byte[] Base32Decode(string input)
    {
        if (string.IsNullOrEmpty(input))
            return new byte[0];

        input = input.Trim().Replace(" ", "").ToUpperInvariant();

        int outputLength = (input.Length * 5) / 8;
        byte[] result = new byte[outputLength];

        const string base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

        int bitBuffer = 0;
        int bitsInBuffer = 0;
        int outputIndex = 0;

        foreach (char c in input)
        {
            int charValue = base32Chars.IndexOf(c);
            if (charValue == -1)
                throw new ArgumentException("Invalid Base32 character: " + c);

            bitBuffer = (bitBuffer << 5) | charValue;
            bitsInBuffer += 5;

            if (bitsInBuffer >= 8)
            {
                result[outputIndex++] = (byte)(bitBuffer >> (bitsInBuffer - 8));
                bitsInBuffer -= 8;
            }
        }

        return result;
    }
}